package org.csu.softwaremetrics_demo.metric.ck;

/**
 * WMC (Weighted Methods per Class) 类
 * 计算类的加权方法数，基于圈复杂度(Cyclomatic Complexity)
 *
 * 该类通过访问以下结构来计算复杂度：
 * 1. 方法声明 (+1)
 * 2. 控制流语句(if, for, while等) (+1)
 * 3. 条件表达式中的逻辑运算符(&&, ||) (+1)
 * 4. case语句 (+1，不包括default)
 * 5. catch子句 (+1)
 *
 * 高WMC值表示：
 * 1. 类可能过于复杂，难以理解和维护
 * 2. 可能需要考虑重构，将复杂度分散到多个类中
 */

import org.apache.commons.lang3.StringUtils;
import org.csu.softwaremetrics_demo.entity.common.CKMetricSingleClass;
import org.csu.softwaremetrics_demo.entity.result.Result;
import org.csu.softwaremetrics_demo.metric.Metric;
import org.eclipse.jdt.core.dom.*;

public class WMC extends ASTVisitor implements Metric<CKMetricSingleClass> {

    /**
     * 存储当前类的圈复杂度总和
     * 每个控制流结构和条件都会增加这个值
     */
    protected int cc = 0;

    /**
     * 访问方法声明节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node 方法声明节点
     * @return 是否继续访问子节点
     */
    public boolean visit(MethodDeclaration node) {
        increaseCc();
        return super.visit(node);
    }


    /**
     * 访问for循环语句节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node for循环语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(ForStatement node) {
        increaseCc();
        return super.visit(node);
    }


    /**
     * 访问增强型for循环语句节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node 增强型for循环语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(EnhancedForStatement node) {
        increaseCc();
        return super.visit(node);
    }


    /**
     * 访问条件表达式节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node 条件表达式节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(ConditionalExpression node) {
        increaseCc();
        return super.visit(node);
    }


    /**
     * 访问do-while循环语句节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node do-while循环语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(DoStatement node) {
        increaseCc();
        return super.visit(node);
    }


    /**
     * 访问while循环语句节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node while循环语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(WhileStatement node) {

        increaseCc();
        return super.visit(node);
    }

    /**
     * 访问switch-case语句节点时调用
     * 如果不是默认case，则增加圈复杂度并继续访问子节点
     * @param node switch-case语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(SwitchCase node) {
        if (!node.isDefault())
            increaseCc();
        return super.visit(node);
    }

    /**
     * 访问初始化块节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node 初始化块节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(Initializer node) {
        increaseCc();
        return super.visit(node);
    }

    /**
     * 访问catch语句节点时调用
     * 增加圈复杂度并继续访问子节点
     * @param node catch语句节点
     * @return 是否继续访问子节点
     */
    @Override
    public boolean visit(CatchClause node) {
        increaseCc();
        return super.visit(node);
    }

    /**
     * 访问if语句节点时调用
     * 计算逻辑运算符的数量并增加相应的圈复杂度
     * 然后继续访问子节点
     *
     * @param node if语句节点
     * @return 是否继续访问子节点
     */
    public boolean visit(IfStatement node) {
        String expr = node.getExpression().toString().replace("&&", "&").replace("||", "|");
        int ands = StringUtils.countMatches(expr, "&");
        int ors = StringUtils.countMatches(expr, "|");

        increaseCc(ands + ors);
        increaseCc();

        return super.visit(node);
    }

    /**
     * 将圈复杂度增加1
     * 用于处理基本控制流结构
     */
    private void increaseCc() {
        increaseCc(1);
    }

    /**
     * 增加指定数量的圈复杂度
     * 用于处理复杂条件表达式（包含多个逻辑运算符）
     *
     * @param qtd 要增加的复杂度数量
     */
    protected void increaseCc(int qtd) {
        cc++;
    }

    /**
     * 执行圈复杂度计算
     * 接受编译单元并开始访问AST节点
     * @param cu 编译单元
     * @param singleMetric 单类度量对象
     * @param result 结果对象
     */
    @Override
    public void execute(CompilationUnit cu, CKMetricSingleClass singleMetric, Result result) {
        cu.accept(this);
    }

    /**
     * 设置单类度量对象的圈复杂度值
     * @param singleMetric 单个度量结果对象
     */
    @Override
    public void setSingleMetric(CKMetricSingleClass singleMetric) {

        singleMetric.setWmc(cc);
    }
}
